package com.ghs.securityprojectboot.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ghs.securityprojectboot.entity.DbAccount;
import com.ghs.securityprojectboot.entity.vo.request.ConfirmResetVO;
import com.ghs.securityprojectboot.entity.vo.request.EmailRegisterVO;
import com.ghs.securityprojectboot.entity.vo.request.ResetPasswordVO;
import com.ghs.securityprojectboot.mapper.DbAccountMapper;
import com.ghs.securityprojectboot.service.IDbAccountService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ghs.securityprojectboot.util.Const;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author shenguohui
 * @since 2024-09-03
 */
@Service
@Slf4j
public class DbAccountServiceImpl  extends ServiceImpl<DbAccountMapper, DbAccount> implements IDbAccountService {
    @Resource
    StringRedisTemplate stringRedisTemplate;
    @Resource
    DbAccountMapper dbAccountMapper;
    @Resource
    MailSender mailSender;
    @Value("${spring.mail.username}")
    String from;
    @Override
    public String sendValidateEmail(String email, boolean hasAccount) {
        String key = Const.VERIFY_EMAIL_DATA + email;
        if(Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
            Long expire = Optional.ofNullable(stringRedisTemplate.getExpire(key, TimeUnit.SECONDS)).orElse(0L);
            if(expire > 120) return "请求频繁，请稍后再试";
        }
        DbAccount account = dbAccountMapper.findAccountByNameOrEmail(email);
        if(hasAccount && account == null) return "没有此邮件地址的账户";
        if(!hasAccount && account != null) return "此邮箱已被其他用户注册";
        Random random = new Random();
        int code = random.nextInt(899999) + 100000;
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(from);
        message.setTo(email);
        message.setSubject("您的验证邮件");
        message.setText("验证码是："+code);
        log.info("验证码是：{}", code);
        try {
            mailSender.send(message);
            stringRedisTemplate.opsForValue().set(key, String.valueOf(code), 3, TimeUnit.MINUTES);
            return null;
        } catch (MailException e) {
            e.printStackTrace();
            return "邮件发送失败，请坚持邮件地址是否有效";
        }
    }


    @Override
    public String validateAndRegister(EmailRegisterVO emailRegisterVO) {
        String email = emailRegisterVO.getEmail();
        String code = stringRedisTemplate.opsForValue().get(Const.VERIFY_EMAIL_DATA + email);
        if(code == null) return "请先获取验证码";
        if(!code.equals(emailRegisterVO.getCode())) return "验证码错误，请重新输入";
        if(this.existsAccountByEmail(email)) return "该邮件地址已被注册";
        String username = emailRegisterVO.getUsername();
        if(this.existsAccountByUsername(username)) return "该用户名已被他人使用，请重新更换";
        String password = new BCryptPasswordEncoder().encode(emailRegisterVO.getPassword());
        DbAccount account = new DbAccount(null, emailRegisterVO.getUsername(),
                password, email, Const.ROLE_DEFAULT, new Date());
        if(!this.save(account)) {
            return "内部错误，注册失败";
        } else {
            stringRedisTemplate.delete(Const.VERIFY_EMAIL_DATA + email);
            return null;
        }
    }

    @Override
    public DbAccount findAccountByNameOrEmail(String text) {
        return this.query()
                .eq("username", text).or()
                .eq("email", text)
                .one();
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        DbAccount account = this.findAccountByNameOrEmail(username);
        if(account == null)
            throw new UsernameNotFoundException("用户名或密码错误");
        return User
                .withUsername(username)
                .password(account.getPassword())
                .roles(account.getRole())
                .build();
    }
    /**
     * 查询指定邮箱的用户是否已经存在
     * @param email 邮箱
     * @return 是否存在
     */
    private boolean existsAccountByEmail(String email){
        return this.baseMapper.exists(Wrappers.<DbAccount>query().eq("email", email));
    }

    /**
     * 查询指定用户名的用户是否已经存在
     * @param username 用户名
     * @return 是否存在
     */
    private boolean existsAccountByUsername(String username){
        return this.baseMapper.exists(Wrappers.<DbAccount>query().eq("username", username));
    }
    /**
     * 重置密码确认操作，验证验证码是否正确
     * @param info 验证基本信息
     * @return 操作结果，null表示正常，否则为错误原因
     */
    @Override
    public String resetConfirm(ConfirmResetVO info) {
        String email = info.getEmail();
        String code = stringRedisTemplate.opsForValue().get(Const.VERIFY_EMAIL_DATA + email);
        if(code == null) return "请先获取验证码";
        if(!code.equals(info.getCode())) return "验证码错误，请重新输入";
        return null;
    }

    @Override
    public String resetPassWord(ResetPasswordVO vo) {
        //校验验证码是否正确
        String verify = resetConfirm(new ConfirmResetVO(vo.getEmail(), vo.getCode()));
        if(verify != null) return verify;
        String email = vo.getEmail();
        String password = new BCryptPasswordEncoder().encode(vo.getPassword());
        //更新密码
        boolean update = this.update().eq("email", email).set("password", password).update();
        if(update) {
            //如果更新成功，删除redis数据
            stringRedisTemplate.delete(Const.VERIFY_EMAIL_DATA + email);
        }
        return update ? null : "更新失败，请联系管理员";
    }
}
